﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Red.Core.DataConsistency
{
    /// <summary>
    /// A class keeping track of consistency of the data being displayed (EditorView classes) with the data stored in the DataCollection (Raw. objects)
    /// It can tell you in a relatively cheap way if you need to refresh/resolve your value again
    /// It does it by storing "captured" values of ConsistencyIDs for all objects and properties that were used to compose the cached value
    /// To check if the cached value is still OK just compare a historic hash computed from those consistencyIDs with the current hash.
    /// Since each operation on source objects is changing their consistencyIDs we can easily tell if token is up to date
    /// </summary>
    public class StateToken
    {
        /// <summary>
        /// Create consistency tracking token for given objects
        /// </summary>
        /// <param name="objects">Objects to track</param>
        public StateToken(List<ConsistentObject> objects, StateGroup stateGroup = null)
        {
            _TrackedObjects = objects.ToArray();
            _ConsistencyHash = CalcHash(_TrackedObjects);

            // group tracking information
            // NOTE: we use number so we don't extend the lifetime of the objects
            if (stateGroup != null)
            {
                _GroupID = stateGroup.GroupID;
                _GroupGeneration = stateGroup.GroupGeneration;

                // enable tracking of object changes for this group
                foreach (var obj in _TrackedObjects)
                    obj.TrackChanges(_GroupID, _GroupGeneration);
            }
        }

        /// <summary>
        /// Is the state of the source objects still considered consistent with whatever we captured from them ?
        /// </summary>
        public bool IsConsistent
        {
            get
            {
                var currentHash = CalcHash(_TrackedObjects);
                return (_ConsistencyHash == currentHash);
            }
        }

        /// <summary>
        /// Externally force the token to be "inconsistent"
        /// </summary>
        public void MakeInconsistent()
        {
            _ConsistencyHash = 0;
        }

        /// <summary>
        /// Make the state of this token again consistent with the source objects
        /// </summary>
        public void MakeConsistent()
        {
            _ConsistencyHash = CalcHash(_TrackedObjects);
        }

        /// <summary>
        /// Internal seed value
        /// </summary>
        private static UInt64 SEED = 42;

        /// <summary>
        /// Compression function for Merkle-Damgard construction.
        /// </summary>
        private static UInt64 Mix(UInt64 x)
        {
            x ^= (x >> 23);
            x *= 0x2127599bf4325c37UL;
            x ^= (x >> 47);
            return x;
        }

        /// <summary>
        /// Calculate consistency hash for given list of objects
        /// </summary>
        /// <param name="objects">Tracked objects</param>
        /// <returns>64-bit hash value representing captured state of the objects</returns>
        private static UInt64 CalcHash(ConsistentObject[] objects)
        {
            UInt64 m = 0x880355f21e6d1965UL;
            UInt64 h = SEED ^ ((UInt64)objects.Length * m);

            foreach (var obj in objects)
            {
                h ^= Mix(obj.ConsistencyID);
                h *= m;
            }

            return Mix(h);
        }

        /// <summary>
        /// Incremental GlobalID for the state group
        /// </summary>
        private UInt32 _GroupID;

        /// <summary>
        /// Always increasing global monotonic ID of group operations
        /// Helps to identify if two GroupIDs actually mean the same group
        /// </summary>
        private UInt32 _GroupGeneration;

        /// Objects for which the view-data consistency should be tracked
        private ConsistentObject[] _TrackedObjects;

        /// Calculated hash last time the objects were considered consistent with data view
        private UInt64 _ConsistencyHash;
    }
}
